﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Shoozla.DataModel.Helper;

namespace Shoozla
{
    public abstract class MultiThreadWorker
    {
        public int ThreadsNum {get; protected set;}
        protected CancellationTokenSource _stopToken;
        protected List<Task> _tasks = new List<Task>();
        public abstract void SplitAndRun(ParameterizedThreadStart func);
        protected TaskFactory _factory;

        public MultiThreadWorker()
        {
           _stopToken = new CancellationTokenSource();
        //   _factory = new TaskFactory(_stopToken.Token,
        //                               TaskCreationOptions.PreferFairness,
        //                               TaskContinuationOptions.PreferFairness,
        //                               TaskScheduler.FromCurrentSynchronizationContext()); //taskscheduler of the UI
           _factory = Task.Factory;
        }
     
        public void StopThreads()
        {
            _stopToken.Cancel();
        }
    }

    public class ListWorker<T> : MultiThreadWorker
    {
        private List<T> _allItems;
        public delegate void ListWorkerDelegate(List<T> list);
        public event EventHandler MultiThreadWorkerDone;
        
        public ListWorker(List<T> items, ThreadingFactorEnum threadingFactor) : base()
        {
            _allItems = items;
            ThreadsNum = ThreadHelper.CalculateTreads(_allItems.Count, threadingFactor);
        }

        public override void SplitAndRun(ParameterizedThreadStart func)
        {
            if (_allItems.Count == 0 || ThreadsNum == 0)
            {
                Trace.WriteLine("[INFO] ListWorker: SplitAndRun: no work to do");
                if (MultiThreadWorkerDone != null)
                    MultiThreadWorkerDone.Invoke(null, null);
                return;
            }

            int blockSize = _allItems.Count / ThreadsNum;
            if (blockSize <= 0)
            {
                blockSize = 1;
                ThreadsNum = _allItems.Count; //go faster --> maximum speed
            }

            var watch = Stopwatch.StartNew();

            Trace.WriteLine("[INFO] ListWorker: SplitAndRun: Threads count: " + ThreadsNum + " blocksize: " + blockSize);
            for (int i = 0; i < ThreadsNum; i++)
            {
                int realBlockSize = 0;

                if (i == 0)
                    realBlockSize = blockSize;
                else
                {
                    if (i * blockSize >= _allItems.Count)
                        realBlockSize = _allItems.Count - (1 - i) * blockSize;
                    else
                        realBlockSize = blockSize;
                }
                //BLOCK of items

                List<T> workingBlock = _allItems.GetRange(i * blockSize, realBlockSize);
                Action<object> a = new Action<object>((o) =>
                {
                    CancellationTokenSource cts = o as CancellationTokenSource;
                    if (!cts.IsCancellationRequested) //to support cancellation
                        func(workingBlock);
                });
                Task t = _factory.StartNew(a, _stopToken, _stopToken.Token);
                _tasks.Add(t);
            }

            _factory.ContinueWhenAll(_tasks.ToArray(),
                     result =>
                     {
                         if (MultiThreadWorkerDone != null)
                             MultiThreadWorkerDone.Invoke(null, null);
                         Trace.WriteLine("Tasks ended:" + watch.Elapsed);
                     }, _stopToken.Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

        }
    }

    public class DictionaryWorker<K,V> : MultiThreadWorker
    {
        private ConcurrentDictionary<K,V> _allItems;
        public delegate void ListWorkerDelegate(Dictionary<K, V> list);
        public event EventHandler MultiThreadWorkerDone;

        public DictionaryWorker(ConcurrentDictionary<K, V> items, ThreadingFactorEnum threadingFactor) : base()
        {
            _allItems = items;
            ThreadsNum = ThreadHelper.CalculateTreads(_allItems.Values.Count,threadingFactor);
        }

        public override void SplitAndRun(ParameterizedThreadStart func)
        {


            if (_allItems.Count == 0 || ThreadsNum == 0)
            {
                Trace.WriteLine("[INFO] DictionaryWorker: SplitAndRun: no work to do");
                if (MultiThreadWorkerDone != null)
                    MultiThreadWorkerDone.Invoke(null, null);
                return;
            }

            int blockSize = _allItems.Count / ThreadsNum;
            if (blockSize <= 0)
            {
                blockSize = 1;
                ThreadsNum = _allItems.Count; //go faster --> maximum speed
            }

            
            var watch = Stopwatch.StartNew();

            Trace.WriteLine("[INFO] DictionaryWorker: SplitAndRun: Threads count: " + ThreadsNum + " blocksize: " + blockSize);
            for (int i = 0; i < ThreadsNum; i++)
            {
                int realBlockSize = 0;

                if (i == 0)
                    realBlockSize = blockSize;
                else
                {
                    if (i * blockSize >= _allItems.Count)
                        realBlockSize = _allItems.Count - (1 - i) * blockSize;
                    else
                        realBlockSize = blockSize;
                }
                //BLOCK of items

                Dictionary<K, V> workingBlock = new Dictionary<K, V>(realBlockSize);
                for (int j = i; j < (i + realBlockSize); j++)
                    workingBlock.Add(_allItems.Keys.ElementAt<K>(j),_allItems.Values.ElementAt<V>(j));

                Action<object> a = new Action<object>((o) =>
                {
                    CancellationTokenSource cts = o as CancellationTokenSource;
                    if (!cts.IsCancellationRequested) //to support cancellation
                        func(workingBlock);
                });
                Task t = _factory.StartNew(a,_stopToken, _stopToken.Token);

                _tasks.Add(t);
            }

            _factory.ContinueWhenAll(_tasks.ToArray(),
                     result =>
                     {
                         if (MultiThreadWorkerDone != null)
                             MultiThreadWorkerDone.Invoke(null, null);
                         Trace.WriteLine("Tasks ended:" + watch.Elapsed);
                     }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

        }
    }
}
